package com.atguigu.service;

import com.atguigu.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 *
 * @author 伟哥
 * @date 2021-05-03 10:54
 */
@Service
public class BookService {
    @Autowired
    BookDao bookDao;


    /**
     * 结账方法
     * @param username 购买用户
     * @param isbn 买的图书id
     *
     *
     * Transactional注解的属性
     *      isolation（需要Isolation）：【事务的隔离级别】，默认值就可防止脏读【具体请看：[尚硅谷]_雷丰阳_第08章 声明式事务.doc】
     *          隔离级别是指若干个【并发的事务之间的隔离程度】。TransactionDefinition 接口中定义了五个表示隔离级别的常量：
     *
     *              ISOLATION_DEFAULT：这是默认值，表示使用底层数据库的默认隔离级别。对大部分数据库而言，通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
     *              ISOLATION_READ_UNCOMMITTED：该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读，不可重复读和幻读，因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
     *              ISOLATION_READ_COMMITTED：该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。
     *              ISOLATION_REPEATABLE_READ：该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
     *              ISOLATION_SERIALIZABLE：所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
     *
     *
     *      propagation（需要Propagation）：【事务的传播行为】（事务的传播 + 事务的行为）【具体请看：[尚硅谷]_雷丰阳_第08章 声明式事务.doc】
     *          所谓事务的传播行为是指，如果在开始当前事务之前，一个事务上下文已经存在（如果有【多个事务进行嵌套运行】，子事务是否和大事务公用一个事务，出错之后是否要和大事务一起回滚），此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量：
     *
     *          PROPAGATION_REQUIRED：【常用】如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是【默认值】。
     *          PROPAGATION_REQUIRES_NEW：【常用】创建一个新的事务，如果当前存在事务，则把当前事务挂起。
     *          PROPAGATION_SUPPORTS：如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。
     *          PROPAGATION_NOT_SUPPORTED：以非事务方式运行，如果当前存在事务，则把当前事务挂起。
     *          PROPAGATION_NEVER：以非事务方式运行，如果当前存在事务，则抛出异常。
     *          PROPAGATION_MANDATORY：如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。
     *          PROPAGATION_NESTED：如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
     *
     *
     *      rollbackFor（需要Class[]）：哪些【异常】事务需要【回滚】
     *      rollbackForClassName（需要String的异常全类名）：
     *
     *      noRollbackFor（需要Class[]）：哪些【异常】事务可以【不回滚】
     *      noRollbackForClassName（需要String的异常全类名）：
     *
     *             异常分类：
     *                  运行时异常（也叫做，非检查异常；默认全部操作都要回滚）：可以不用处理
     *                  编译时异常（也叫做，检查异常；默认不回滚）：必须处理，try catch或者throw
     *
     *             事务的回滚：默认发生 【运行时异常都回滚】
     *                        默认发生 【编译时异常不会回滚】
     *
     *             noRollbackFor：哪些异常可以不回滚，可以让原来回滚的异常（RuntimeException），不回滚
     *                  @Transactional(timeout = 3,noRollbackFor = {ArithmeticException.class,NullPointerException.class})
     *                  在注解中{}，代表数组；这句话意思是：算数异常、空指针异常不回滚
     *             noRollbackForClassName：使用异常的全类名
     *                  @Transactional(timeout = 3,noRollbackForClassName = {"java.lang.ArithmeticException"})
     *
     *             rollbackFor：原本不进行回滚的【编译时异常，例如FileNotFoundException】，让其进行回滚
     *                  @Transactional(timeout = 3,rollbackFor = {FileNotFoundException.class})
     *
     *             rollbackForClassName：异常使用的全类名
     *                  @Transactional(timeout = 3,rollbackForClassName = {"java.io.FileNotFoundException"})
     *
     *
     *      timeout（需要int，秒为单位）：超时时间：事务超出指定执行时长后自动终止并回滚
     *          @Transactional(timeout = 3)
     *      readOnly（需要Boolean）：设置事务为只读（事务只会查询，不会进行增删改）
     *          可以进行事务优化。【加快查询速度】，无需设置提交，数据库加锁等等
     *          @Transactional(timeout = 3,readOnly = true)
     *
     *
     * 数据库Transactional注解的用法，由问题【方法【xxxx】需要在Transactional注解指定rollbackFor或者在方法中显示的rollback】引入_ostar_liang的专栏-CSDN博客_transactional注解的使用
     * https://blog.csdn.net/ostar_liang/article/details/80770837
     *
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void checkout(String username,String isbn) {


        // 0、查询图书价格
        int price = bookDao.getPrice(isbn);

        // 1、减去用户余额
        bookDao.updateBalance(username,price);

        /*
         编译时异常【默认不回滚】：FileNotFoundException
         new FileInputStream("D://hhhh.haha");
         */

        // 2、减库存
        bookDao.updateStock(isbn);

        /*
        运行时异常【默认回滚】：数学异常
        int i = 1/0;
         */



    }

    /**
     * 更新图书价格
     * @param isbn 此书在数据库中的id
     * @param price 需要修改的价格
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateBookPrice(String isbn,int price){
        bookDao.updateBookPrice(isbn, price);
        //int i =10/0;
    }

    /**
     * 在bookService内，调用bookService的方法
     * 结果：整体都回滚，REQUIRES_NEW不生效
     */
    @Transactional
    public void mulTX(){
        //REQUIRES_NEW
        checkout("Tom","ISBN-001");

        //REQUIRES_NEW
        updateBookPrice("ISBN-002",998);

        int i = 1/0;

    }
}
